﻿using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace regex
{
    public class Samples
    {
        public static void Run()
        {
            ValidatingInput();
            WordBoundaries();
            LocationMatching();
            WildCards();
            ShorthandChars();
            Grouping();

            // Exercises from the Book
            Exercise1();
            Exercise2();
            Exercise3();
        }

        private static void Exercise3()
        {
            /* Using the Regex code from Exercise one, reformat the phone number into:
             * (###) ###-####
             */

            Debug.Assert(FormatTelNum("(555)555-1212").Equals("(555) 555-1212"));
            Debug.Assert(FormatTelNum("(555) 555-1212").Equals("(555) 555-1212"));
            Debug.Assert(FormatTelNum("555-555-1212").Equals("(555) 555-1212"));
            Debug.Assert(FormatTelNum("5555551212").Equals("(555) 555-1212"));
        }

        static string FormatTelNum(string input)
        {
            string pattern = @"^\(?(\d{3})\)?[\s\-]?(\d{3})\-?(\d{4})$";
            var m = Regex.Match(input, pattern);
            var rtn = string.Format("({0}) {1}-{2}", m.Groups[1], m.Groups[2], m.Groups[3]);
            return rtn;
        }

        private static void Exercise2()
        {
            /* Using one line of code, return true if any of the formats are used:
             * 01111
             * 01111-1111
             */

            string pattern = @"^\d{5}(\-\d{4})?$";
            var r = new Regex(pattern);

            Debug.Assert(r.IsMatch("01111"));
            Debug.Assert(r.IsMatch("01111-1111"));
        }

        private static void Exercise1()
        {
            /* Create a RegEx that returns true for any of the following Tel. no. formats:
             * (555)555-1212
             * (555) 555-1212
             * 555-555-1212
             * 5555551212
             */

            string pattern = @"^\(?\d{3}\)?[\s\-]?\d{3}\-?\d{4}$";
            var r = new Regex(pattern);
            Debug.Assert(r.IsMatch("(555)555-1212"));
            Debug.Assert(r.IsMatch("(555) 555-1212"));
            Debug.Assert(r.IsMatch("555-555-1212"));
            Debug.Assert(r.IsMatch("555-555-1212"));
        }

        private static void Grouping()
        {
            /* Grouping can be used to reference matched data at a later point.
             * The format "(?<name>pattern)" is used.
             * for example:
             * foo(?<mid>boo|bar)loo
             * would match the strings "foobooloo" and foobarloo", the group "mid"
             * would return "boo" and "bar" respectively.
             * 
             * \k is used to reference a named group.             * 
             */
            string pattern = @"foo(?<mid>boo|bar)loo\k<mid>";
            var r = new Regex(pattern);

            Debug.Assert(r.IsMatch("fooboolooboo"));
            Debug.Assert(r.IsMatch("foobarloobar"));

            Debug.Assert(!r.IsMatch("foobooloobar"));
            Debug.Assert(!r.IsMatch("foobarlooboo"));

            // e.g. \k<char> will return whatever was returned from the "char" group.
        }

        private static void ShorthandChars()
        {
            /* \d any digit e.g. [0-9]
             * \D any non-digit [^0-9]
             * \s any whitespace [\f\n\r\t\v]
             * \S any non-whitespace [^\f\n\r\t\v]
             * \w any word (inc. underscore) [A-Za-z0-9_]
             */

            // UK Postcode (one format) "AA00 0AA"
            const string pattern = @"[A-Za-z]{2}\d{2}\s\d[A-Za-z]{2}";
            var r = new Regex(pattern);

            Debug.Assert(r.IsMatch("AA00 0AA"));
            Debug.Assert(r.IsMatch("aa00 0aa"));
            Debug.Assert(r.IsMatch("BZ19 8DQ"));
            Debug.Assert(!r.IsMatch("BZ198DQ"));
            Debug.Assert(!r.IsMatch("BZ1 98DQ"));
            Debug.Assert(!r.IsMatch("1BZ9 8DQ"));
        }

        private static void WildCards()
        {
            /* * Matches preceeding char 0 or more times
             *   "to*n" matches "ton", "tn" and "toooooon"
             * 
             * + Matches preceeding char 1 or more times
             *   "to+n" matches "ton" "toooon" but not "tn"
             * 
             * {x} where "X" is the number of times to repeat preceedng char
             *     "to{2}n" matches "toon" but not "tn" or "tooon".
             * 
             * {min, max} to specify a range of repeated chars. Max can be ommitted to leave open-ended.
             *            "to{1,3}n" matches "ton", "toon" and "tooon" but not "tn" or "toooon".
             * 
             * ? marks a char as optional
             *   "to?n" matches "tn" and "ton" but not "toon".
             * 
             * . for any single char
             *   "to.n" matches "toyn" and "totn" but not "ton" or "tn".
             *   
             * [ ] classes match several chars
             *     "t[oi]n" matches "tin" and "ton" but not "ten".
             *     
             * [a-b] range classes can be used for a range of chars
             *       "t[a-c]n" matches "tan" "tbn" and "tcn" but not "tdn" or "tzn".
             *       
             * x|y matches x OR y
             *     "t(a|i)n" matches "tan" or "tin" but not "ton".
             */

            // to match "Rob" with up to 5 o's and either lower/upper case R.
            const string pattern = @"(R|r)o{1,5}b";
            var r = new Regex(pattern);
            Debug.Assert(r.IsMatch("Rob"));
            Debug.Assert(r.IsMatch("rob"));
            Debug.Assert(r.IsMatch("Rooooob"));
            Debug.Assert(r.IsMatch("rooooob"));
            Debug.Assert(!r.IsMatch("Roooooob"));
            Debug.Assert(!r.IsMatch("roooooob"));
            Debug.Assert(!r.IsMatch("rb"));
            Debug.Assert(!r.IsMatch("Rb"));
            Debug.Assert(!r.IsMatch("Rab"));
        }

        private static void LocationMatching()
        {
            /*  \A Specifies that the match must begin at the first character of
             *     the string (ignores multiple lines).
             *     "\A Tes" Matches: "Test" but not "A Test"
             */
            var firstChar = new Regex(@"\ATes");
            Debug.Assert(firstChar.IsMatch("Test"));
            Debug.Assert(!firstChar.IsMatch("A Test"));

            /* \Z Specifies the match must end at either the last char or before
             *    \n at the end of the string (ignores multiple lines).
             *    "Test\Z" matches: "A Test" but not "A Tester".
             */
            var lastChar = new Regex(@"Test\Z");
            Debug.Assert(lastChar.IsMatch("A Test"));
            Debug.Assert(!lastChar.IsMatch("A Tester"));

            /* \z Specifies the match must end at the last char of the 
             *    string (ignores multiple lines).
             *    "Test\z" matches: "A Test" but not "A Tester".
             */
            var lastCharNoLineBreak = new Regex(@"Test\z");
            Debug.Assert(lastCharNoLineBreak.IsMatch("A Test"));
            Debug.Assert(!lastCharNoLineBreak.IsMatch("A Tester"));
        }

        private static void WordBoundaries()
        {
            /* "Word Boundaries" specify how the expression is to match in relation to words.
             * \b matches a word boundary
             *    e.g. "car\b" matches "car" and "tocar" but not "carbs"
             * \B matches a nonword boundary
             *    e.g "car\B" matches "carbs" but not "tocar"
             */

            var rb = new Regex(@"car\b");
            var rB = new Regex(@"car\B");

            Debug.Assert(rb.IsMatch("car"));
            Debug.Assert(!rb.IsMatch("carbs"));

            Debug.Assert(rB.IsMatch("carbs"));
            Debug.Assert(!rB.IsMatch("tocar"));
        }

        private static void ValidatingInput()
        {
            /* Always ensure you include the leading and ending characters
             * "^" and "$" respectively.
             * e.g
             * ^\d{5}$ will match "12345" but not "a12345b".
             * \d{5} will match "12345" but also match "a12345b"
             */
            var r1 = new Regex(@"^\d{5}$");
            var r2 = new Regex(@"\d{5}");

            Debug.Assert(r1.IsMatch("12345"));
            Debug.Assert(!r1.IsMatch("a12345b"));

            Debug.Assert(r2.IsMatch("12345"));
            Debug.Assert(r2.IsMatch("a12345b"));
        }

    }
}
